/*****************************************************************************\
    FILE: security.cpp

    DESCRIPTION:
        Helpers functions to check if an Automation interface or ActiveX Control
    is hosted or used by a safe caller.

    BryanSt 8/25/1999
    Copyright (C) Microsoft Corp 1999-1999. All rights reserved.
\*****************************************************************************/
#include "stock.h"
#pragma hdrstop

#include <mshtml.h>


/***************************************************************\
    DESCRIPTION:
        We are given a site via IObjectWithSite.  Obtain the hosting
    IHTMLDocument from there.  This is typically used to get a URL from
    in order to check zones or ProcessUrlAction() attributes, or if there are
    two URLs you can compare their zones from cross-zone restrictions.
\***************************************************************/
STDAPI GetHTMLDoc2(IUnknown *punk, IHTMLDocument2 **ppHtmlDoc)
{
    *ppHtmlDoc = NULL;

    if (!punk)
        return E_FAIL;
        
    *ppHtmlDoc = NULL;
    //  The window.external, jscript "new ActiveXObject" and the <OBJECT> tag
    //  don't take us down the same road.

    IOleClientSite *pClientSite;
    HRESULT hr = punk->QueryInterface(IID_PPV_ARG(IOleClientSite, &pClientSite));
    if (SUCCEEDED(hr))
    {
        //  <OBJECT> tag path
        IOleContainer *pContainer;

        // This will return the interface for the current FRAME containing the
        // OBJECT tag.  We will only check that frames security because we
        // rely on MSHTML to block cross frame scripting when it isn't safe.
        hr = pClientSite->GetContainer(&pContainer);
        if (SUCCEEDED(hr))
        {
            hr = pContainer->QueryInterface(IID_PPV_ARG(IHTMLDocument2, ppHtmlDoc));
            pContainer->Release();
        }
    
        if (FAILED(hr))
        {
            //  window.external path
            IWebBrowser2 *pWebBrowser2;
            hr = IUnknown_QueryService(pClientSite, SID_SWebBrowserApp, IID_PPV_ARG(IWebBrowser2, &pWebBrowser2));
            if (SUCCEEDED(hr))
            {
                IDispatch *pDispatch;
                hr = pWebBrowser2->get_Document(&pDispatch);
                if (SUCCEEDED(hr))
                {
                    hr = pDispatch->QueryInterface(IID_PPV_ARG(IHTMLDocument2, ppHtmlDoc));
                    pDispatch->Release();
                }
                pWebBrowser2->Release();
            }
        }
        pClientSite->Release();
    }
    else
    {
        //  jscript path
        hr = IUnknown_QueryService(punk, SID_SContainerDispatch, IID_PPV_ARG(IHTMLDocument2, ppHtmlDoc));
    }

    ASSERT(FAILED(hr) || (*ppHtmlDoc));

    return hr;
}


/***************************************************************\
    DESCRIPTION:
        This function is supposed to find out the zone from the
    specified URL or Path.
\***************************************************************/
STDAPI LocalZoneCheckPath(LPCWSTR pszUrl, IUnknown * punkSite)
{
    DWORD dwZoneID = URLZONE_UNTRUSTED;
    HRESULT hr = GetZoneFromUrl(pszUrl, punkSite, &dwZoneID);
    
    if (SUCCEEDED(hr))
    {
        if (dwZoneID == URLZONE_LOCAL_MACHINE)
            hr = S_OK;
        else
            hr = E_ACCESSDENIED;
    }

    return hr;
}

/***************************************************************\
    DESCRIPTION:
        Get the zone from the specified URL or Path.
\***************************************************************/
STDAPI GetZoneFromUrl(LPCWSTR pszUrl, IUnknown * punkSite, DWORD * pdwZoneID)
{
    HRESULT hr = E_FAIL;
    if (pszUrl && pdwZoneID) 
    {
        IInternetSecurityManager * pSecMgr = NULL;

        // WARNING: IInternetSecurityManager is the guy who translates
        //   from URL->Zone.  If we CoCreate this object, it will do the
        //   default mapping.  Some hosts, like Outlook Express, want to
        //   over ride the default mapping in order to sandbox some content.
        //   I beleive this could be used to force HTML in an email
        //   message (C:\mailmessage.eml) to act like it's from a more
        //   untrusted zone.  We use QueryService to get this interface
        //   from our host.  This info is from SanjayS. (BryanSt 8/21/1999)
        hr = IUnknown_QueryService(punkSite, SID_SInternetSecurityManager, IID_PPV_ARG(IInternetSecurityManager, &pSecMgr));
        if (FAILED(hr))
        {
            hr = CoCreateInstance(CLSID_InternetSecurityManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IInternetSecurityManager, &pSecMgr));
        }
        
        if (SUCCEEDED(hr))
        {
            hr = pSecMgr->MapUrlToZone(pszUrl, pdwZoneID, 0);
            ATOMICRELEASE(pSecMgr);
        }
    }
    else 
    {
        hr = E_INVALIDARG;
    }
    return hr;
}


/***************************************************************\
    DESCRIPTION:
        We are given a site via IObjectWithSite.  See if that host
    maps to the Local Zone.
\***************************************************************/
STDAPI LocalZoneCheck(IUnknown *punkSite)
{
    DWORD dwZoneID = URLZONE_UNTRUSTED;
    HRESULT hr = GetZoneFromSite(punkSite, &dwZoneID);
    
    if (SUCCEEDED(hr))
    {
        if (dwZoneID == URLZONE_LOCAL_MACHINE)
            hr = S_OK;
        else
            hr = E_ACCESSDENIED;
    }

    return hr;
}

STDAPI GetZoneFromSite(IUnknown *punkSite, DWORD *pdwZoneID)
{
    //  Return S_FALSE if we don't have a host site since we have no way of doing a 
    //  security check.  This is as far as VB 5.0 apps get.
    if (!punkSite)
    {
        *pdwZoneID = URLZONE_UNTRUSTED;
        return S_FALSE;
    }

    HRESULT hr = E_ACCESSDENIED;
    BOOL fTriedBrowser = FALSE;

    // Try to find the original template path for zone checking
    IOleCommandTarget * pct;
    if (SUCCEEDED(IUnknown_QueryService(punkSite, SID_DefView, IID_PPV_ARG(IOleCommandTarget, &pct))))
    {
        VARIANT vTemplatePath;
        vTemplatePath.vt = VT_EMPTY;
        if (pct->Exec(&CGID_DefView, DVCMDID_GETTEMPLATEDIRNAME, 0, NULL, &vTemplatePath) == S_OK)
        {
            fTriedBrowser = TRUE;
            if (vTemplatePath.vt == VT_BSTR)
            {
                hr = GetZoneFromUrl(vTemplatePath.bstrVal, punkSite, pdwZoneID);
            }

            // We were able to talk to the browser, so don't fall back on Trident because they may be
            // less secure.
            fTriedBrowser = TRUE;
            VariantClear(&vTemplatePath);
        }
        pct->Release();
    }

    // If this is one of those cases where the browser doesn't exist (AOL, VB, ...) then
    // we will check the scripts security.  If we did ask the browser, don't ask trident
    // because the browser is often more restrictive in some cases.
    if (!fTriedBrowser && (hr != S_OK))
    {
        // Try to use the URL from the document to zone check 
        IHTMLDocument2 *pHtmlDoc;

        /***************************************************************\
         NOTE:
         1. If punkSite points into an <IFRAME APPLICATION="yes"> in a
            HTA file, then the URL GetHTMLDoc2() returns
            is for the IFRAME SRC..
         2. If this isn't an HTML container, then we can't calculate a zone, so it's E_ACCESSDENIED.
        \***************************************************************/
        if (SUCCEEDED(GetHTMLDoc2(punkSite, &pHtmlDoc)))
        {
            BSTR bstrURL;

            /***************************************************************\
             QUESTION:
             1. If this HTML container isn't safe but it's URL maps to the
                Local Zone, then we have a problem.  This may happen with
                email messages, especially if they are saved to a file.
                If the user reopens a saved .eml file, it will be hosted in
                it's mail container that may support the IInternet interface
                to indicate that it's in an untrusted zone.  Will we get
                a Local Zone URL in that case?
             ANSWER:
             1. The container can override zone mappings by supporting 
                IInternetSecurityManager.

             QUESTION:
             2. What if there are multiple frames in different zones. 
                Will trident block cross frame scripting?
             ANSWER:
             2. Yes.
            \***************************************************************/
            if (SUCCEEDED(pHtmlDoc->get_URL(&bstrURL)))
            {
                // NOTE: the above URL is improperly escaped, this is
                // due to app compat. if you depend on this URL being valid
                // use another means to get this

                hr = GetZoneFromUrl(bstrURL, punkSite, pdwZoneID);
                SysFreeString(bstrURL);
            }
            pHtmlDoc->Release();
        }
    }
                            
    return hr;
}

